﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading;
using Netbits.Common.Collections;
using Netbits.Common.Conditions;
using Netbits.Common.General;
using Netbits.Common.Threading;

namespace Netbits.Common.Databases.MemoryTree
{
	/// <summary>
	/// Represents a memory-tree-db
	/// </summary>
	public sealed class MemoryTreeDb : IDisposable
	{
		#region Private Variables

		int _SaveCounter;
		string _EncryptionPassword;
		MemoryTreeNode _Root;
		Encoding _Encoder = Encoding.UTF8;
		bool _MemoryDb;
		Lock _Lock;
		string _DatabaseFilename;
		Dictionary<Guid, MemoryTreeNode> _IndexedNodes;
		bool _Disposed;

		#endregion

		#region Constructor

		/// <summary>
		/// Create a new instance of the memory-tree-db implementation
		/// This database operates only in the system-memory
		/// </summary>
		public MemoryTreeDb()
		{
			_Lock = new Lock();
			_MemoryDb = true;
			_IndexedNodes = new Dictionary<Guid, MemoryTreeNode>();
			_Root = new MemoryTreeNode( this, null );
			_Root.SetField( "mt-id", _Root.Id );
			_IndexedNodes.Add( _Root.Id, _Root );
		}

		/// <summary>
		/// Create a new instance of the memory-tree-db implementation
		/// This database safe the memory-tree-db to the specified database-file
		/// </summary>
		/// <param name="DatabaseFilename_">The database-file to load from or save to</param>
		public MemoryTreeDb( string DatabaseFilename_ )
		{
			_DatabaseFilename = DatabaseFilename_;
			_IndexedNodes = new Dictionary<Guid, MemoryTreeNode>();
			_Lock = new Lock();
			_MemoryDb = false;
		}

		#endregion

		#region Public Properties

		/// <summary>
		/// Get or set the encryption password 
		/// If the encryption password is set encryption is activate
		/// </summary>
		public string EncryptionPassword
		{
			get
			{
				using( _Lock.Read() )
					return _EncryptionPassword;
			}
			set
			{
				using( _Lock.Write() )
					_EncryptionPassword = value;
			}
		}

		/// <summary>
		/// Get or set the string encoding system
		/// </summary>
		public Encoding Encoder
		{
			get
			{
				using( _Lock.Read() )
					return _Encoder;
			}
			set
			{
				using( _Lock.Write() )
					_Encoder = value;
			}
		}

		/// <summary>
		/// Returns the root node 
		/// </summary>
		public MemoryTreeNode Root
		{
			get
			{
				if( _Root == null )
					throw new InvalidOperationException( "you must load the database first!" );
				return _Root;
			}
		}

		/// <summary>
		/// Returns the memory-tree-db id
		/// </summary>
		public Guid Id
		{
			get
			{
				if( _Root == null )
					throw new InvalidOperationException( "database not loaded" );
				return _Root.Id;
			}
		}

		/// <summary>
		/// Returns the name of the memory-tree-db or set this.
		/// </summary>
		public string Name
		{
			get
			{
				if( _Root == null )
					throw new InvalidOperationException( "database not loaded" );

				return _Root.GetField<string>( "mt-db-name", string.Empty );
			}
			set
			{
				if( _Root == null )
					throw new InvalidOperationException( "database not loaded" );

				_Root.SetField( "mt-db-name", value );
			}
		}

		/// <summary>
		/// Returns the database-filename
		/// </summary>
		public string DatabaseFilename
		{
			get
			{
				return _DatabaseFilename;
			}
		}

		#endregion

		#region Public Functions

		/// <summary>
		/// Load the database from specified database-filename
		/// Must called for new database.
		/// </summary>
		public void Load()
		{
			if( _Root != null )
				throw new InvalidOperationException( "database already loaded" );

			if( string.IsNullOrEmpty( _DatabaseFilename ) )
				throw new Exception( "database filename not set" );

			if( File.Exists( _DatabaseFilename ) )
			{
				FileStream fs = File.OpenRead( _DatabaseFilename );
				byte[] buffer = new byte[fs.Length];
				fs.Read( buffer, 0, buffer.Length );
				fs.Close();
				Load( buffer );
			}
			else
			{
				Load( null );
			}
		}

		/// <summary>
		/// Load the database from specified buffer. 
		/// </summary>
		/// <param name="Buffer_">Load this specified buffer</param>
		public void Load( byte[] Buffer_ )
		{
			if( Buffer_ != null )
			{
				byte[] buffer = Buffer_;

				string encryptPassword;
				using( _Lock.Read() )
					encryptPassword = _EncryptionPassword;

				if( !string.IsNullOrEmpty( encryptPassword ) )
					buffer = Crypto.Decrypt( Buffer_, _EncryptionPassword );

				_Load( buffer );
			}
			else
				_Load( null );
		}

		/// <summary>
		/// Save the database to file.
		/// </summary>
		public void Save()
		{
			byte[] buffer = ToByteArray();

			FileStream fs = File.OpenWrite( _DatabaseFilename );
			fs.Write( buffer, 0, buffer.Length );
			fs.Flush();
			fs.Close();
		}

		/// <summary>
		/// Unload this database and dispose all objects.
		/// </summary>
		public void Unload()
		{
			if( _Root != null )
			{
				_Root.Dispose();
				_Root = null;
			}
		}

		/// <summary>
		/// Returns the representing bytearray.
		/// </summary>
		/// <returns>byte[]</returns>
		public byte[] ToByteArray()
		{
			return ToByteArray( -1 );
		}

		/// <summary>
		/// Returns the representing byte-array with specified levels-count 
		/// </summary>
		/// <param name="Levels_">The level counts</param>
		/// <returns>The representing byte-arry</returns>
		public byte[] ToByteArray( int Levels_ )
		{
			string text = _Root.ToString( Levels_ );
			string encryptionPassword;
			using( _Lock.Read() )
				encryptionPassword = _EncryptionPassword;

			byte[] bytes = _Encoder.GetBytes( text );
			if( !string.IsNullOrEmpty( encryptionPassword ) )
				return Crypto.Encrypt( bytes, _EncryptionPassword );
			else
				return bytes;
		}

		/// <summary>
		/// Return the string that representing this database.
		/// </summary>
		/// <param name="Levels_">returns the specified amount of levels</param>
		/// <returns>The string that representing this database</returns>
		public string ToString( int Levels_ )
		{
			return _Root.ToString( Levels_ );
		}

		/// <summary>
		/// Find the specified memory-tree-node by his id
		/// </summary>
		/// <param name="Id_">Id to search for</param>
		/// <returns>The instance otherwise null</returns>
		public MemoryTreeNode Find( Guid Id_ )
		{
			Condition.Requires( Id_, "Id_" ).IsNotEmpty();

			return _IndexedNodes[Id_];
		}

		#endregion

		#region Internal Functions

		internal void __SetMustSave()
		{
			if( !_MemoryDb )
				Interlocked.Increment( ref _SaveCounter );
		}

		internal void __AddNode( MemoryTreeNode Node_ )
		{
			_IndexedNodes.Add( Node_.Id, Node_ );
			__SetMustSave();
		}

		internal void __RemoveNode( MemoryTreeNode Node_ )
		{
			_IndexedNodes.Remove( Node_.Id );
			__SetMustSave();
		}

		internal void __Save()
		{
			if( _SaveCounter > 0 )
			{
				Save();
				Interlocked.Decrement( ref _SaveCounter );
			}
		}

		#endregion

		#region Private Functions

		void _Load( byte[] EncryptedBuffer_ )
		{
			try
			{
				_Lock.EnterWriteLock();

				_Root = new MemoryTreeNode( this, null );
				if( EncryptedBuffer_ != null )
					_Root.__FromText( _Encoder.GetString( EncryptedBuffer_ ), 0 );

				_IndexedNodes.Add( _Root.Id, _Root );
			}
			finally
			{
				_Lock.ExitWriteLock();
			}
		}

		#endregion

		#region Overrides

		/// <summary>
		/// Returns the full-string for this database-instance.
		/// </summary>
		/// <returns>The represent string.</returns>
		public override string ToString()
		{
			return _Root.ToString( -1 );
		}

		#endregion

		#region IDisposable

		/// <summary>
		/// Dispose this database
		/// </summary>
		public void Dispose()
		{
			if( !_Disposed )
			{
				if( _IndexedNodes != null )
					_IndexedNodes.Clear();

				if( _Root != null )
				{
					_Root.Dispose();
					_Root = null;
				}

				if( _Lock != null )
				{
					_Lock.Dispose();
					_Lock = null;
				}

				_Disposed = true;
			}

			GC.SuppressFinalize( this );
		}

		#endregion
	}
}
